﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

using SharedGenomics.Workbench.Core;
using SharedGenomics.Workbench.Views;
using SharedGenomics.Workbench.PresentationModel;
using SharedGenomics.Workbench.Services;
using SharedGenomics.Domain;

namespace SharedGenomics.Workbench.Presenters
{
    public class JobSubmissionPresenter : Presenter
    {
        private readonly IEnumerable<JobType> BINARY_JOB_TYPES = new JobType[] { JobType.PassocBT, JobType.PepiBT, JobType.FastPepiBT, JobType.PModel, JobType.PCMH };
        private readonly IEnumerable<JobType> QUANTITATIVE_JOB_TYPES = new JobType[] { JobType.PassocQT, JobType.PepiQT };

        private IJobOptionsPresenter _currentJobOptionsPresenter;
        private readonly IJobOptionsPresenterFactory _jobOptionsPresenterFactory;
        private readonly IFileSchemaRepository _schemaRepository;

        public JobSubmissionPresenter(IJobSubmissionView view, IDataSetRepository dataSets, IJobService jobService, IJobOptionsPresenterFactory presenterFactory, IFileSchemaRepository schemaRepository)
            : base(view)
        {
            //initialise view
            view.IsSubmitProgressBarVisible = false;

            //register for view events
            view.Loaded += new EventHandler(this.View_Loaded);
            view.RefreshDataSetsClicked += (o, e) => { this.LoadAvailableDataSets(); };

            view.SubmitClicked += new EventHandler(this.View_SubmitClicked);
            view.SelectedDataSetSummaryChanged += new EventHandler(this.View_SelectedDataSetSummaryChanged);

            //set private members
            this.SubmissionView = view;
            this.DataSets = dataSets;
            this.JobService = jobService;
            this._jobOptionsPresenterFactory = presenterFactory;
            this._schemaRepository = schemaRepository;
        }

        private void View_Loaded(object sender, EventArgs e)
        {
            //setup presentation transfer object
            this.PTO = new JobSubmissionPTO();
            this.PTO.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(this.PTO_PropertyChanged);
            this.PTO.AvailablePermutationCounts = new short[] { 0, 10000, 20000 };

            this.PTO.ValidationChanged += (o, vea) => { this.View.ValidationErrors = vea.ValidationErrors; };

            //set view items
            this.SubmissionView.PTO = this.PTO;

            this.LoadAvailableDataSets();
        }

        private void View_SelectedDataSetSummaryChanged(object sender, EventArgs e)
        {
            DataSetSummary selectedSummary = this.SubmissionView.SelectedDataSetSummary;
            int fileSchemaId = selectedSummary.FileSchemaId;

            //try get data set - if this returns a value then set various view properties
            this._schemaRepository.GetSchemaById(fileSchemaId).BindAction(schema =>
            {
                this.SelectedDataSetSchema = schema;
                this.PTO.Phenotypes = schema.PhenotypeColumns.Select(c => c.ToValueDisplayItem());
                this.PTO.StratificationColumns = schema.CategoricalColumns.Select(c => c.ToValueDisplayItem()).ToArray();
                this.PTO.SelectedPhenotype = selectedSummary.PhenotypeColumnName;

                this.SetCategoricalFilterSummaries(selectedSummary);
            });
        }

        private IEnumerable<JobType> GetAvailableJobTypes(bool binaryPhenotype)
        {
            return binaryPhenotype
                ? this.BINARY_JOB_TYPES
                : this.QUANTITATIVE_JOB_TYPES;
        }

        private IEnumerable<JobType> GetAvailableJobTypes(FileSchemaColumn column)
        {
            Debug.Assert(column != null);

            NominalFileSchemaColumn nominalColumn = column as NominalFileSchemaColumn;
            bool isBinary = nominalColumn != null && nominalColumn.IsBinary;
            return this.GetAvailableJobTypes(isBinary);
        }

        private void SetCategoricalFilterSummaries(DataSetSummary currentSummary)
        {
            IEnumerable<CategoryFilterSummary> cfSummaries = currentSummary == null
                ? Enumerable.Empty<CategoryFilterSummary>()
                : currentSummary.CategoryFilters;

            this.SubmissionView.CategoryFilters = cfSummaries.Select(cf =>
            {
                string filterName = cf.FilterColumnLabel??cf.FilterColumnName;
                string filterValues = String.Join(",", (cf.FilterValues??Enumerable.Empty<string>()).ToArray());
                return new ValueDisplayItem<string>(filterName, filterValues);
            });
        }

        private void View_SubmitClicked(object sender, EventArgs e)
        {
            if (this.PTO.Validate() && this._currentJobOptionsPresenter.Validate())
            {
                JobInfo jobInfo = this._currentJobOptionsPresenter.CreateJob();

                this.SubmissionView.IsSubmitEnabled = false;
                this.SubmissionView.IsSubmitProgressBarVisible = true;
                this.SubmissionView.SubmitMessage = "Job submission in progress...";

                this.BackgroundExecutor.Execute(() =>
                {
                    this.JobService.SubmitJob(jobInfo);
                },
                or =>
                {
                    this.SubmissionView.IsSubmitEnabled = true;
                    this.SubmissionView.IsSubmitProgressBarVisible = false;

                    //display status of job submission
                    if (or.Error == null)
                    {
                        this.SubmissionView.SubmitMessage = "Job submission complete - you will receive an email notification when the job has completed";
                    }
                    else
                    {
                        this.SubmissionView.SubmitMessage = String.Format("Job submission failed:{0}{1}", Environment.NewLine, or.Error.Message);
                    }
                });
            }
        }

        private void PTO_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "SelectedJobType":
                    this.SelectedJobTypeChanged();
                    break;

                case "SelectedPhenotype":
                    this.SelectedPhenotypeChanged();
                    break;

                default:
                    break;
            }
        }

        protected virtual void SelectedJobTypeChanged()
        {
            //JobType? jobType = this.PTO.SelectedJobType;
            JobType jobType = this.PTO.SelectedJobType.Value;
            this._currentJobOptionsPresenter = this._jobOptionsPresenterFactory.GetPresenterForJobType(this.PTO);
            this.SubmissionView.JobOptionsView = this._currentJobOptionsPresenter.View;
        }

        protected void SelectedPhenotypeChanged()
        {
            Debug.Assert(this.SelectedDataSetSchema != null);

            string selectedPhenotype = this.PTO.SelectedPhenotype;
            IEnumerable<ValueDisplayItem<JobType>> availableJobTypes = null;

            if (selectedPhenotype != null)
            {
                var phenotypeColumn = this.SelectedDataSetSchema[selectedPhenotype];
                availableJobTypes = this.GetAvailableJobTypes(phenotypeColumn).Select(jt => new ValueDisplayItem<JobType>(jt, jt.GetDisplayString()));
            }
            else
            {
                availableJobTypes = new ValueDisplayItem<JobType>[] { };
            }

            this.PTO.JobTypes = availableJobTypes;
        }

        protected virtual void LoadAvailableDataSets()
        {
            try
            {
                //set available datasets on the view if the repository returns a value
                this.DataSets.GetDataSetSummaries().BindAction(summaries =>
                {
                    this.SubmissionView.DataSets = summaries;
                });
            }
            catch (Exception ex)
            {
                this.View.DisplayError(String.Format("Unable to fetch user data sets: {0}", ex.Message));
            }
        }

        private IJobSubmissionView SubmissionView { get; set; }
        protected virtual JobSubmissionPTO PTO { get; private set; }
        protected FileSchema SelectedDataSetSchema { get; set; }
        private IDataSetRepository DataSets { get; set; }
        private IJobService JobService { get; set; }
    }
}
